﻿/*
 * Cratos Desktop
 * Cratos Cache Engine class
 * 
 * Copyright (c) 2011-2012 Draco
 * 
 * This file is part of Cratos.
 * 
 * Cratos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * Cratos is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cratos.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *       .==.        .==.
 *      //'^\\      //^'\\
 *     // ^ ^\(\__/)/^ ^^\\
 *    //^ ^^ ^/6  6\ ^^ ^ \\
 *   //^ ^^ ^/( .. )\^ ^ ^ \\
 *  // ^^ ^/\| V""V|/\^ ^ ^ \\
 * //^^/\/ /  '~~'  \ \/\^ ^ \\
 *  ------------------------------------
 *  HERE BE DRAGONS
 *  
 *  Johan Dercksen - johan@draco.co.za
 */
using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using Cratos.Tools.General;
using Cratos.Desktop.Caching;
using Cratos.Tools.DataObject;

namespace Cratos.Desktop
{
	/// <summary>
	/// Cratos Cache Action enum declaration
	/// </summary>
	public enum CratosCacheAction
	{
		/// <summary>
		/// Exception
		/// </summary>
		Exception,

		/// <summary>
		/// Informational message
		/// </summary>
		Informational,

		/// <summary>
		/// Save Database
		/// </summary>
		SaveDatabase,

		/// <summary>
		/// Load Cache Success
		/// </summary>
		LoadSuccess
	}
	
	/// <summary>
	/// Cratos Cache Event Handler delegate declaration
	/// </summary>
	/// <param name="cacheEngine">Cache Engine</param>
	/// <param name="cacheAction">Cache Action</param>
	/// <param name="eventMsg">Cache Event message</param>
	public delegate void CratosCacheEventHandler(CratosCacheEngine cacheEngine, CratosCacheAction cacheAction, string eventMsg);

	/// <summary>
	/// Cratos Cache Engine class
	/// </summary>
	public class CratosCacheEngine
	{
		#region Class Constant declarations
		#endregion

		#region Class Event declarations
		/// <summary>
		/// Cratos Cache Event declaration
		/// </summary>
		public event CratosCacheEventHandler CacheEvent;
		#endregion

		#region Class Variable declarations
		private bool								isStartup;
		private int									ruleTimerInterval;
		private System.Timers.Timer ruleTimer;
		private object							cacheCollection;
		#endregion

		#region Class Constructors & Destructors
		/// <summary>
		/// Class Constructor
		/// </summary>
		public CratosCacheEngine(CancellationToken cancelToken, string newName, int newTimerInterval, dsCratosCache cacheDB)
		{
			// Set the relevant default values
			this.isStartup = true;

			// Save the relevant value(s)
			this.Name              = newName;
			this.CancelToken			 = cancelToken;
			this.ruleTimerInterval = newTimerInterval;
			this.CacheDB					 = cacheDB;
		}
		#endregion

		#region Class Properties
		/// <summary>
		/// Cancellation Token
		/// </summary>
		public CancellationToken CancelToken { get; set; }

		/// <summary>
		/// Engine Name
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Cache Collection
		/// </summary>
		public dsCratosCache CacheDB { get; set; }

		/// <summary>
		/// Force Reload indicator
		/// </summary>
		public bool ForceReload { get; set; }

		#region Cache Item Properties
		/// <summary>
		/// Cache Data Object Type
		/// </summary>
		public Type CacheDOType { get; set; }

		/// <summary>
		/// Cache Expiry
		/// </summary>
		public DateTime CacheExpiry { get; set; }

		/// <summary>
		/// Paging Load support indicator
		/// </summary>
		public bool CacheIsPaging { get; set; }

		/// <summary>
		/// Paging Max Records
		/// </summary>
		public int CachePagingMaxRecords { get; set; }

		/// <summary>
		/// Cache DB Connection Name
		/// </summary>
		public string CacheDBConnectionName { get; set; }

		/// <summary>
		/// Cache DB Action Type
		/// </summary>
		public DOCratosActionType CacheDBActionType { get; set; }

		/// <summary>
		/// Update Cache on Startup indicator
		/// </summary>
		public bool CacheUpdateOnStartup { get; set; }

		/// <summary>
		/// Cache Expiry See
		/// </summary>
		public long CacheExpirySeed { get; set; }
		#endregion
		#endregion

		#region Class Methods
		/// <summary>
		/// Start Rule Engine processing
		/// </summary>
		public void Start()
		{
			// Declare the required variables

			try
			{
				// Create and Setup the Cache Collection object
				cacheCollection = Activator.CreateInstance(this.CacheDOType);

				// Create, Setup and start the Rule Timer
				this.ruleTimer          = new System.Timers.Timer();
				this.ruleTimer.Interval = 1000;
				this.ruleTimer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnRuleTimeElapsed);

				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Informational, "[Cache: " + this.Name + "] Cache processing started");

				// Execute the Rules
				this.OnRuleTimeElapsed(null, null);

				// Start the infinite loop ...
				while (!this.CancelToken.IsCancellationRequested) { Thread.Sleep(1000); }
			}
			catch (Exception runtimeException)
			{
				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Exception, 
													string.Format("[Cache: {0}] {1}", this.Name, runtimeException.ToString()));
			}
			finally
			{
				// Stop the Rule Engine Timer
				if (this.ruleTimer != null) { this.ruleTimer.Stop(); }

				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Informational, "[Cache: " + this.Name + "] Cache processing stopped");
			}

			// Return to calling method
			return;
		}

		/// <summary>
		/// Class String representation
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			// Return the relevant data
			return(this.Name);
		}
		#endregion

		#region Class Support Methods
		private void OnCacheEvent(CratosCacheEngine cacheEngine, CratosCacheAction cacheAction, string eventMsg)
		{
			// Declare the required variables

			// Raise the relevant event if required
			if (this.CacheEvent != null) { this.CacheEvent(cacheEngine, cacheAction, eventMsg); }

			// Return to calling method
			return;
		}

		private void OnRuleTimeElapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			// Declare the required variables

			try
			{
				// Stop the Rule Timer
				this.ruleTimer.Stop();

				// Determine if we have to Refresh the Cache Item
				if ((this.isStartup && this.CacheUpdateOnStartup) || (this.CacheExpiry <= DateTime.Now))
				{
					// Load the Cache Item from the server
					if (this.LoadCacheItemFromServer())
					{
						this.ruleTimer.Interval = (this.ruleTimerInterval * 1000);
					}
					else
					{
						this.ruleTimer.Interval = (1000 * 5);
					}
				}

				// Update the Startup indicator
				this.isStartup = false;
			}
			catch (Exception runtimeException)
			{
				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Exception,
													string.Format("[Cache: {0}] {1}", this.Name, runtimeException.ToString()));
			}
			finally
			{
				// Start the Rule Timer
				this.ruleTimer.Start();
			}

			// Return to calling method
			return;
		}

		private bool LoadCacheItemFromServer()
		{
			// Declare the required variables
			bool returnValue = false;

			try
			{
				// Handle Paging loaded if required
				if (this.CacheIsPaging)
				{
					#region Paging Load
					//  // Create and setup the Raptor CDO List
					//  cdoList																			= new CdoRaptorList<DORaptorBase>();
					//  cdoList.DataObjectList.DataObject						= (DORaptorBase)Activator.CreateInstance(Type.GetType(currentCacheItem.Cache_DOType, true));
					//  cdoList.IgnoreCache													= true;
					//  cdoList.IsCacheRetrieve											= true;
					//  cdoList.DataObjectList.RestrictResultCount	= true;
					//  cdoList.DataObjectList.MaxNoRecords					= currentCacheItem.Cache_PagingMaxRecords;
					//  cdoList.DataObjectList.CurrentPage					= 0;

					//  // Set the Connection Name if different
					//  if (!currentCacheItem.IsCache_ConnectionNull()) { cdoList.ConnectionName = currentCacheItem.Cache_Connection; }

					//  // Retrieve all the data (One page at a time)
					//  do
					//  {
					//    // Update the relevant data
					//    cdoList.DataObjectList.ListAction = DORaptorListAction.Retrieve;
					//    cdoList.DataObjectList.CurrentPage++;

					//    // Send the request to the server
					//    if (cdoList.PerformAction())
					//    {
					//      lock (this.m_CacheDB)
					//      {
					//        // Process all the returned data
					//        foreach (DORaptorBase currentItem in cdoList.DataObjectList)
					//        {
					//          // Create and setup a new item
					//          newItemRow							= this.m_CacheDB.CacheItems.NewCacheItemsRow();
					//          newItemRow.Cache_ID			= currentCacheItem.Cache_ID;
					//          newItemRow.Cache_Object = CdoHelpers.CreateSerializeStream(currentItem, true);

					//          // Add the object to the list
					//          this.m_CacheDB.CacheItems.AddCacheItemsRow(newItemRow);
					//        }

					//        // Update the Cache Item
					//        currentCacheItem.Cache_Expiry				= DateTime.Now.AddMinutes(this.m_CacheExpirySeed);
					//        currentCacheItem.Cache_LastUpdated	= DateTime.Now;

					//        // Commit all changes
					//        this.m_CacheDB.AcceptChanges();
					//      }
					//    }
					//    else
					//    {
					//      // Log the error
					//      RaptorHelper.LogMessage(RaptorLogSeverity.Exception, "Raptor Caching",
					//                              string.Format("LoadCacheItemFromServer [{0}]", cdoList.Errors[0]), null, false);
					//    }
					//  } while (cdoList.DataObjectList.PageCount > cdoList.DataObjectList.CurrentPage);
					#endregion
				}
				else
				{
					#region Normal Loading
					// Log a message to the UI
					this.OnCacheEvent(this, CratosCacheAction.Informational, string.Format("[Cache: {0}] Start loading ...", this.Name));

					// Set the Connection Name if different
					if (!string.IsNullOrEmpty(this.CacheDBConnectionName))
					{
						DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(cacheCollection, "DataObject"),
																						"DBConnectionName", this.CacheDBConnectionName);
					}

					// Set the DB Action Type
					DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(cacheCollection, "DataObject"),
																					"DBActionType", this.CacheDBActionType);

					// Setup the Relevant Data Object
					DOCratosHelper.SetPropertyValue(cacheCollection, "CollectionAction", DOCratosCollectionAction.Retrieve);
					DOCratosHelper.SetPropertyValue(cacheCollection, "IsCacheRetrieve", true);

					// Send the request to the server
					if ((bool)DOCratosHelper.InvokeMethod(cacheCollection, "PerformAction", null))
					{
						// Update the Cache Expiry
						this.CacheExpiry = DateTime.Now.AddMinutes(this.CacheExpirySeed);

						// Log a message to the UI
						this.OnCacheEvent(this, CratosCacheAction.LoadSuccess, "");
						this.OnCacheEvent(this, CratosCacheAction.Informational, string.Format("[Cache: {0}] Successful loading", this.Name));

						// Process all the returned data
						returnValue = this.AddCacheDataObjectItems((List<IDOCratos>)DOCratosHelper.InvokeMethod(cacheCollection, "GetList", null));
					}
					else
					{
						// Log a message to the UI
						this.OnCacheEvent(this, CratosCacheAction.Informational, string.Format("[Cache: {0}] Error occurred", this.Name));

						// Log a message to the UI
						this.OnCacheEvent(this, CratosCacheAction.Exception,
															string.Format("[Cache: {0}] {1}", this.Name, DOCratosHelper.GetPropertyValue(cacheCollection, "ErrorMessage").ToString()));
					}
					#endregion
				}
			}
			catch (Exception runtimeException)
			{
				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Exception,
													string.Format("[Cache: {0}] {1}", this.Name, runtimeException.ToString()));
			}

			// Return to calling method
			return(returnValue);
		}

		private bool AddCacheDataObjectItems(List<IDOCratos> allDOItems)
		{
			// Declare the required variables
			bool												returnValue = false;
			dsCratosCache.CacheItemsRow newItemRow;

			try
			{
				// Lock the Cache
				lock (CratosCache.CacheDBLock)
				{
					// Search for the relevant specified item
					foreach (dsCratosCache.CacheRow currentCacheItem in this.CacheDB.Cache.Rows)
					{
						// Validate the DO Name
						if (this.CacheDOType.AssemblyQualifiedName == currentCacheItem.Cache_DOType)
						{
							// Delete all the Cache Items
							foreach (dsCratosCache.CacheItemsRow currentItemRow in
																										this.CacheDB.CacheItems.Select(string.Format("Cache_ID = {0}", currentCacheItem.Cache_ID)))
							{
								currentItemRow.Delete();
							}
							this.CacheDB.AcceptChanges();

							// Load the Items into the DB
							foreach (IDOCratos currentItem in allDOItems)
							{
								// Create and setup a new item
								newItemRow              = this.CacheDB.CacheItems.NewCacheItemsRow();
								newItemRow.Cache_ID     = currentCacheItem.Cache_ID;
								newItemRow.Cache_Object = currentItem.CreateSerializeStream();

								// Add the object to the list
								this.CacheDB.CacheItems.AddCacheItemsRow(newItemRow);
							}

							// Update the Cache Item
							currentCacheItem.Cache_Expiry      = DateTime.Now.AddMinutes(this.CacheExpirySeed);
							currentCacheItem.Cache_LastUpdated = DateTime.Now;

							// Commit all changes and break out of the loop
							this.CacheDB.AcceptChanges();
							break;
						}
					}
				}

				// Save the Cache Database
				this.OnCacheEvent(this, CratosCacheAction.SaveDatabase, string.Empty);

				// Save the Database Changes
				returnValue = true;
			}
			catch (Exception runtimeException)
			{
				// Log a message to the UI
				this.OnCacheEvent(this, CratosCacheAction.Exception,
													string.Format("[Cache: {0}] {1}", this.Name, runtimeException.ToString()));
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion
	}
}
